package ru.myrtle.umf.query.condition;

import java.util.regex.Pattern;

public abstract class UStringCondition extends UValueCondition<String, UStringAdapter> {

    /**
     * A <code>String<code> related condition class that compares a <code>String<code>
     * value to another. The arguments being evaluated are adapted to a <code>String<code>
     * first using a <code>StringAdapter</code>  and then the string will be compared with
     * the initialization string  to see if they are equal.
     */
    public static class Value extends UStringCondition {

        private final String value;
        private final boolean caseSensitive;

        /**
         * A simple constructor that takes an initialization
         * <code>String<code> to match against.
         * It defaults to using the StringAdapter.DEFAULT for adapting the evaluated
         * object to string before matching them, and it defaults to being case-sensitive.
         *
         * @param string the initialization <code>String<code> to use for equality testing
         */
        public Value(String string) {
            this(string, true);
        }

        /**
         * A simple constructor that takes an initialization
         * <code>String<code> to match against.
         * It defaults to using the StringAdapter.DEFAULT for adapting the evaluated
         * object to string before matching them.
         *
         * @param value the initialization <code>String<code> to use for equality testing
         * @param caseSensitive a boolean value specifying whether to use case in matching strings
         */
        public Value(String value, boolean caseSensitive) {
            this(
                    value, caseSensitive, UStringAdapter.DEFAULT
            );
        }

        /**
         * A simple constructor that takes an initialization
         * <code>String<code> to match against,
         * a <code>StringAdapter</code> for adapting the evaluated object to string before matching
         * them and a boolean flag indicating whether the pattern matching
         * should be case-sensitive or not.
         *
         * @param value the initialization <code>String<code> to use for equality testing
         * @param caseSensitive a boolean value specifying whether to use case in matching strings
         * @param adapter The <code>StringAdapter</code> to use to get a <code>String<code> out of evaluated Objects
         */
        public Value(String value, boolean caseSensitive, UStringAdapter adapter) {
            super(
                    adapter
            );
            this.value = value;
            this.caseSensitive = caseSensitive;
        }

        /**
         * A getter function for the case-sensitive value used by this
         * <code>StringValue</code> condition
         *
         * @return boolean indicating whether or not to compare string in
         *         case-sensitive manner or not.
         */
        protected final boolean isCaseSensitive() {
            return caseSensitive;
        }

        static boolean equals(Object value1st, Object value2nd) {
            if ((value1st == null) != (value2nd == null)) {
                return false;
            }

            if (value1st == null) {
                return true;
            }

            return value1st.equals(value2nd);
        }

        @Override
        public boolean isSatisfiedImpl(String string) {
            if (string != null && caseSensitive) {
                string = string.toUpperCase();
            }

            return equals(
                    value, string
            );
        }
    }

    /**
     * A Condition object that tests for <code>String<code> length values. The arguments
     * being evaluated are adapted to a <code>String<code> first using a <code>StringAdapter</code>
     * and then the length of the string will be compared with the initialization numeric values to
     * see if it is equal/in-range.
     */
    public static class StringLength extends UStringCondition {

        private final int upperBound;
        private final int lowerBound;

        /**
         * A simple constructor that takes one int argument to test strings' length
         * against It evaluates to <code>true</code> when the evaluated
         * <code>String<code> length equals the length
         * initialization argument. It defaults to using the StringAdapter.DEFAULT
         * for adapting the evaluated object to string before getting their length.
         *
         * @param length The int value that the evaluated string length must match
         */
        public StringLength(int length) {
            this(length, UStringAdapter.DEFAULT);
        }

        /**
         * A constructor that takes one int argument to test strings' length against
         * and a <code>StringAdapter</code>. It evaluates to <code>true</code>
         * when the evaluated <code>String<code> length equals the length
         * initialization argument. It uses the argument <code>StringAdapter</code>
         * for adapting the evaluated object to string before getting their length.
         *
         * @param length The int value that the evaluated string length must match
         * @param adapter The <code>StringAdapter</code> to use to get a <code>String<code> out of evaluated Objects
         */
        public StringLength(int length, UStringAdapter adapter) {
            this(length, length, adapter);
        }

        /**
         * A constructor that takes two int arguments representing a range to test
         * strings' length against. It evaluates to <code>true</code> when the
         * evaluated <code>String<code> length is equal to any
         * these two int values passed or fall in between.
         * It defaults to using the StringAdapter.DEFAULT
         * for adapting the evaluated object to string before getting their length.
         *
         * @param lowerBound the int value representing the lower-bound in range testing
         * @param upperBound the int value representing the upper-bound in range testing
         */
        public StringLength(int lowerBound, int upperBound) {
            this(lowerBound, upperBound, UStringAdapter.DEFAULT);
        }

        /**
         * A constructor that takes two int arguments representing a range to test
         * strings' length against. It evaluates to <code>true</code> when the
         * evaluated <code>String<code> length is equal to any
         * these two int values passed or fall in between. It uses the argument <code>StringAdapter</code>
         * for adapting the evaluated object to string before getting their length.
         *
         * @param lowerBound the int value representing the lower-bound in range testing
         * @param upperBound the int value representing the upper-bound in range testing
         * @param adapter The <code>StringAdapter</code> to use to get a <code>String<code> out of evaluated Objects
         */
        public StringLength(int lowerBound, int upperBound, UStringAdapter adapter) {
            super(adapter);
            this.lowerBound = lowerBound;
            this.upperBound = upperBound;
        }

        @Override
        public boolean isSatisfiedImpl(String str) {
            return ((str.length() >= lowerBound) && (str.length() <= upperBound));
        }
    }

    /**
     * A <code>StringValue</code> condition subclass that tests for
     * <code>String<code> using regular expression.
     * The arguments being evaluated are adapted to a <code>String<code> first using a <code>StringAdapter</code>
     * and then the string will be compared with the initialization regular expression
     * to see if it matches.
     *
     */
    public static class RegularExpression extends UStringCondition {

        private final Pattern pattern;
        private final boolean caseSensitive;

        /**
         * A simple constructor that takes the regular expression to use for
         * matching. It defaults to using the StringAdapter.DEFAULT for adapting the
         * evaluated object to string before matching them, and it defaults to being
         * case-sensitive.
         *
         * @param patternStr
         *            The regular expression pattern to use for matching strings
         *            when evaluating this <code>StringRegularExpressionValue</code>
         *            condition
         */
        public RegularExpression(String patternStr) {
            this(
                    patternStr, UStringAdapter.DEFAULT
            );
        }

        /**
         * A constructor that takes the regular expression to use for matching, and
         * a <code>StringAdapter</code> for adapting the evaluated object to
         * string before matching them. It defaults to being case-sensitive.
         *
         * @param patternStr
         *            The regular expression pattern to use for matching strings
         *            when evaluating this <code>StringRegularExpressionValue</code>
         *            condition.
         * @param adapter
         *            The <code>StringAdapter</code> to use to get a
         *            <code>String<code> out of evaluated Objects
         */
        public RegularExpression(String patternStr, UStringAdapter adapter) {
            this(
                    patternStr, true, adapter
            );
        }

        /**
         * A constructor that takes the regular expression to use for matching, a
         * <code>StringAdapter</code> for adapting the evaluated object to string
         * before matching them and a boolean flag indicating whether the pattern
         * matching should be case-sensitive or not.
         *
         * @param patternStr
         *            The regular expression pattern to use for matching strings
         *            when evaluating this <code>StringRegularExpressionValue</code>
         *            condition.
         * @param caseSensitive
         *            a boolean value specifying whether to use case in matching
         *            strings
         * @param adapter
         *            The <code>StringAdapter</code> to use to get a
         *            <code>String<code> out of evaluated Objects
         */
        public RegularExpression(String patternStr, boolean caseSensitive, UStringAdapter adapter) {
            super(adapter);

            this.caseSensitive = caseSensitive;
            this.pattern = caseSensitive
                    ? Pattern.compile(patternStr)
                    : Pattern.compile(patternStr, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
        }

        @Override
        public boolean isSatisfiedImpl(String str) {
            return getPattern().matcher(str).find();
        }

        /**
         * A getter for the <code>Pattern</code> used by this
         * <code>StringRegularExpressionValue</code>
         *
         * @return Pattern The <code>Pattern</code> generated by compiling the
         *         regular expression
         */
        protected final Pattern getPattern() {
            return pattern;
        }
    }

    public UStringCondition() {
        this(
                UStringAdapter.DEFAULT
        );
    }

    public UStringCondition(UStringAdapter valueAdapter) {
        super(valueAdapter);
    }
}
